<template>
  <div>
    <!-- 如何添加消失动画？ -->
    <!-- 设定 v-move class transition: all 200ms 开启动画 -->
    <!-- 在消失之前设定 position:absolute 更改位置-->
    <!-- 就会有transform动画出现 -->
    <t-slide-transition-group
      class="common-item-list"
      tag="div"
      :name="animated ? (draging ? 'common-item-drag' : 'common-item') : ''"
      :duration="animated ? 200 : 0"
    >
      <div
        v-for="commonItem in dataCommonItems"
        :key="commonItem.__ghost ? 'ghost' : commonItem.id"
        v-fake-drag="{
          dragstart: e => dragstart(e, commonItem),
          dragenter: e => dragenter(e, commonItem),
          dragend: e => dragend(e, commonItem),
          mousemove: e => mousemove(e, commonItem),
          handle: '.common-item-container'
        }"
      >
        <div v-if="commonItem.__ghost" class="ghost">
          <div></div>
        </div>
        <common-item
          v-else
          :item="commonItem"
          :selected="selected.includes(commonItem.id)"
          :editing="editing === commonItem.id"
          @on-outer-click="onCommonOuterClick(commonItem.id)"
          @on-inner-click="e => onCommonInnerClick(commonItem.id, e)"
          @on-inner-dblclick="onCommonInnerDbllick(commonItem.id)"
          @on-close="!(editing = '') && !(selected = [commonItem.id])"
        />
      </div>
    </t-slide-transition-group>
    <!-- TODO: 以后将这个飘着的东西挪到指令里面，等学会了如何弄右键菜单 -->
    <div
      id="drag-float"
      v-if="draging"
      :style="`top:${drag.y}px;left:${drag.x}px;`"
    >
      <div class="item" :class="{ multi: selected.length > 1 }">
        {{
          bakCommonItems.length > 0
            ? bakCommonItems[0].title || "新建待办事项"
            : "没有内容"
        }}
      </div>
      <div class="badge">{{ selected.length }}</div>
    </div>
  </div>
</template>
<script>
import { KeyBinding, KeyCode, KeyMod } from "@winwin/keybinding.js";
import COMMON_ITEM_REMOVE_BY_IDS from "../graphql/CommonItemRemoveByIds.gql";
import COMMON_ITEMS_UPDATE from "../graphql/CommonItemsUpdate.gql";
import DATA_ALL from "../graphql/DataAll.gql";
import CommonItem from "./CommonItem";
import { toCtrlCmd } from "../utils/key";
import throttle from "lodash/throttle";
import Dispatcher from "../utils/dispatcher";
export default {
  name: "CommonItemList",
  components: {
    CommonItem
  },
  props: {
    value: {
      type: Array,
      default: () => []
    },
    animated: {
      type: Boolean,
      requied: true
    }
  },
  data() {
    return {
      dataCommonItems: this.value,
      // drag
      bakCommonItems: [],
      draging: false,
      ghostIdx: void 0,
      ghostObj: { __ghost: true },
      drag: { x: 0, y: 0 },
      // selected
      selected: [],
      lastSelected: "",
      editing: ""
    };
  },
  watch: {
    value(v) {
      this.dataCommonItems = v;
    },
    dataCommonItems(v) {
      this.$emit("input", v);
    },
    ["$route.path"]() {
      this.editing = "";
    }
  },
  computed: {
    lastSelectedIdx() {
      const o = this.dataCommonItems
        .map((o, idx) => [o.id, idx])
        .filter(o => o[0] === this.lastSelected);
      if (o) return [0][1];
      return -1;
    },
    selectedIdxs() {
      return this.dataCommonItems
        .map((o, idx) => [o.id, idx])
        .filter(a => this.selected.includes(a[0]))
        .map(a => a[1]);
    },
    sorteddataCommonItems() {
      return JSON.parse(JSON.stringify(this.dataCommonItems))
        .map((v, idx) => {
          v.index = idx;
          return v;
        })
        .filter(item => !item.__ghost);
    }
  },
  mounted() {
    this.mousemove = throttle(this.mousemove, 10);
    Dispatcher.register(
      "commonitem.remove",
      () => {
        this.removeCommonItemByIds();
      },
      () => {
        return this.selected.length > 0;
      }
    );
    Dispatcher.register(
      "commonitem.selected.all",
      () => {
        this.selected = this.dataCommonItems.map(o => o.id);
      },
      () => {
        if (this.draging) return false;
        if (this.editing) return false;
        return true;
      }
    );
    Dispatcher.register(
      "commonitem.selected.up",
      () => {
        const first =
          this.selectedIdxs.length > 0
            ? Math.min(...this.selectedIdxs)
            : this.dataCommonItems.length;
        this.selected = [
          this.dataCommonItems[first === 0 ? first : first - 1].id
        ];
      },
      () => {
        if (this.editing) return false;
        if (this.selected.length === 1 && this.selectedIdxs[0] === 0)
          return false;
        return true;
      }
    );
    Dispatcher.register(
      "commonitem.selected.down",
      () => {
        const last =
          this.selectedIdxs.length > 0 ? Math.max(...this.selectedIdxs) : -1;
        this.selected = [
          this.dataCommonItems[
            last === this.dataCommonItems.length - 1 ? last : last + 1
          ].id
        ];
      },
      () => {
        if (this.editing) return false;
        if (
          this.selected.length === 1 &&
          this.selectedIdxs[0] === this.dataCommonItems.length - 1
        )
          return false;
        return true;
      }
    );
    KeyBinding.register("window", "remove.backspace", KeyCode.Backspace, () => {
      Dispatcher.dispatch("commonitem.remove");
    });
    KeyBinding.register("window", "remove.delete", KeyCode.Delete, () => {
      Dispatcher.dispatch("commonitem.remove");
    });
    KeyBinding.register("window", "edit", KeyCode.Enter, () => {
      if (this.selected.length === 1 && !this.editing)
        this.editing = this.selected[0];
    });
    KeyBinding.register("window", "select.up", KeyCode.UpArrow, () => {
      Dispatcher.dispatch("commonitem.selected.up");
    });
    KeyBinding.register("window", "select.down", KeyCode.DownArrow, () => {
      Dispatcher.dispatch("commonitem.selected.down");
    });
    KeyBinding.register(
      "window",
      "select.all",
      KeyMod.CtrlCmd | KeyCode.KEY_A,
      () => {
        Dispatcher.dispatch("commonitem.selected.all");
      }
    );
  },
  beforeDestroy() {
    KeyBinding.unregister("window", "remove.backspace");
    KeyBinding.unregister("window", "remove.delete");
    KeyBinding.unregister("window", "select.all");
    KeyBinding.unregister("window", "select.up");
    KeyBinding.unregister("window", "select.down");
    Dispatcher.unregister("commonitem.remove");
    Dispatcher.unregister("commonitem.selected.all");
    Dispatcher.unregister("commonitem.selected.up");
    Dispatcher.unregister("commonitem.selected.down");
  },
  methods: {
    //#region drag
    mousemove({ e }) {
      if (!this.draging) return;
      this.drag.x = e.clientX;
      this.drag.y = e.clientY;
    },
    dragstart({ e }) {
      if (this.selected.length < 1) return;
      this.bakCommonItems = this.dataCommonItems.filter(o =>
        this.selected.includes(o.id)
      );
      this.bakCommonItems.forEach(o => {
        this.dataCommonItems.splice(this.dataCommonItems.indexOf(o), 1);
      });
      this.ghostIdx = this.dataCommonItems.length;
      this.dataCommonItems.push(this.ghostObj);
      this.draging = true;
      this.drag.x = e.clientX;
      this.drag.y = e.clientY;
      document.body.style.cursor = "move";
    },
    dragenter(_, item) {
      if (!this.draging) return;
      const idx = this.dataCommonItems.map(o => o.id).indexOf(item.id);
      this.dataCommonItems.splice(this.ghostIdx, 1);
      this.dataCommonItems.splice(idx, 0, this.ghostObj);
      this.ghostIdx = idx;
    },
    dragend() {
      if (!this.draging) return;
      this.dataCommonItems.splice(this.ghostIdx, 1);
      this.bakCommonItems.map(o => {
        this.dataCommonItems.splice(this.ghostIdx, 0, o);
        this.ghostIdx++;
      });
      this.bakCommonItems = [];
      this.draging = false;
      this.ghostIdx = void 0;
      document.body.style.cursor = null;
      this.updateCommonItems();
    },
    //#endregion
    //#region CURD
    removeCommonItemByIds() {
      const ids = JSON.parse(JSON.stringify(this.selected));
      this.$apollo
        .mutate({
          mutation: COMMON_ITEM_REMOVE_BY_IDS,
          variables: {
            ids
          },
          update: (store, { data: { removeCommonItemByIds } }) => {
            const data = store.readQuery({ query: DATA_ALL });
            data.commonItems = removeCommonItemByIds.commonItems;
            data.projects = removeCommonItemByIds.projects;
            store.writeQuery({ query: DATA_ALL, data });
          }
        })
        .then(() => {
          ids.forEach(id => {
            if (id == this.editing) this.editing = "";
            if (this.selected.includes(id)) {
              this.selected.splice(this.selected.indexOf(id), 1);
            }
          });
        })
        .catch(error => {
          console.error(error);
          this.$notify({
            title: "删除失败",
            type: "error"
          });
        });
    },
    updateCommonItems() {
      const commonitems = this.sorteddataCommonItems.map(
        ({ id, title, note, parent, done, index }) => {
          return {
            id,
            title,
            note,
            parent,
            done,
            index
          };
        }
      );
      this.$apollo
        .mutate({
          mutation: COMMON_ITEMS_UPDATE,
          variables: {
            items: commonitems
          },
          update: (store, { data: { updateCommonItems } }) => {
            const data = store.readQuery({ query: DATA_ALL });
            data.commonItems = updateCommonItems.commonItems;
            store.writeQuery({ query: DATA_ALL, data });
          },
          optimisticResponse: {
            __typename: "Mutation",
            updateCommonItems: {
              __typename: "CommonItemsUpdateResponse",
              commonItems: this.sorteddataCommonItems.map(o => {
                return {
                  __typename: "CommonItem",
                  ...o
                };
              })
            }
          }
        })
        .catch(error => {
          this.dataCommonItems = JSON.parse(JSON.stringify(this.commonItems));
          console.error(error);
          this.$notify({
            title: "保存失败",
            type: "error"
          });
        });
    },
    //#endregion
    //#region interaction
    onCommonOuterClick(id) {
      this.selected = [];
      if (id === this.editing) {
        this.selected.push(id);
        this.lastSelected = id;
      }
      this.editing = "";
    },
    onCommonInnerClick(id, e) {
      const cmdCtrl = toCtrlCmd(e.ctrlKey, e.metaKey);
      const shift = e.shiftKey;
      const idx = this.selected.indexOf(id);
      if (this.editing === id) return;
      if (cmdCtrl) {
        if (idx > -1) this.selected.splice(idx, 1);
        else {
          this.selected.push(id);
          this.lastSelected = id;
        }
      } else if (shift && this.selected.length > 0) {
        const last = this.sorteddataCommonItems.filter(
          item => this.lastSelected === item.id
        )[0].index;
        const current = this.sorteddataCommonItems.filter(
          item => id === item.id
        )[0].index;
        const start = Math.min(last, current);
        const end = Math.max(last, current);
        this.selected = [];
        this.sorteddataCommonItems
          .filter(item => item.index >= start && item.index <= end)
          .forEach(item => this.selected.push(item.id));
      } else {
        this.selected = [];
        {
          this.selected.push(id);
          this.lastSelected = id;
        }
      }
      this.editing = "";
    },
    onCommonInnerDbllick(id) {
      if (this.editing === id) return;
      this.selected = [];
      this.editing = id;
    },
    //#endregion
    //#region methods
    clearSelected() {
      this.selected = [];
      this.editing = "";
    },
    setEditing(id) {
      this.selected = [];
      this.editing = id;
    }
    //#endregion
  }
};
</script>
<style lang="less">
.common-item-list > div,
.common-item-move {
  transition: all ease-out 200ms;
}
.common-item-drag-move {
  transition: all ease-out 50ms;
}
.common-item-enter-active,
.common-item-leave-active {
  transition: all ease-out 200ms;
  .common-item {
    transition: all ease-out 200ms;
  }
}
.common-item-leave-active {
  .common-item {
    position: absolute;
    width: 100%;
  }
}
.common-item-leave {
  opacity: 1 !important;
  .common-item {
    width: 100%;
  }
}
.common-item-enter,
.common-item-leave-to {
  opacity: 0 !important;
  .common-item {
    padding-top: 0 !important;
    padding-bottom: 0 !important;
  }
}
.common-item-list {
  .ghost {
    margin-left: -8px;
    padding: 1px 0;
    & > div {
      max-width: 82%;
      margin: 4px auto;
      background-color: #f0f1f3;
      border-radius: 3px;
      height: 28px;
    }
  }
}
</style>
<style lang="less" scoped>
#drag-float {
  position: fixed;
  top: 0;
  left: 0;
  user-select: none;
  pointer-events: none;
  transition: all linear 10ms;
  z-index: 9999;
  .badge {
    position: absolute;
    background-color: #eb3324;
    color: white;
    height: 20px;
    width: 20px;
    overflow: hidden;
    font-size: 12px;
    font-weight: bolder;
    border-radius: 50%;
    transform: translate(100%, -28px);
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .item {
    font-size: 14px;
    padding: 0 8px;
    width: 400px;
    max-width: 80vw;
    background-color: #cee2fc;
    border: 1px solid #c8dbf4;
    border-radius: 3px;
    height: 28px;
    line-height: 28px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
    transform: translate(-20%, -50%);
    &.multi {
      box-shadow: #cee2fc55 3px 3px 0 0, #c8dbf455 4px 4px 0 0,
        #cee2fc11 7px 7px 0 0, #c8dbf411 8px 8px 0 0;
    }
  }
}
</style>
